agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH] Asymmetric partitionwise join.
399+ messages / 2 participants
[nested] [flat]

* [PATCH] Asymmetric partitionwise join.
@ 2021-04-02 06:02  Andrey Lepikhov <a.lepikhov@postgrespro.ru>
  0 siblings, 0 replies; 399+ messages in thread

From: Andrey Lepikhov @ 2021-04-02 06:02 UTC (permalink / raw)

Teach optimizer to consider partitionwise join of non-partitioned
table with each partition of partitioned table.

Disallow asymmetric machinery for joining of two partitioned (or appended)
relations because it could cause huge consumption of CPU and memory
during reparameterization of NestLoop path.

Change logic of the multilevel child relids adjustment, because this
feature allows the optimizer to plan in new way.
---
 src/backend/optimizer/path/joinpath.c        |   9 +
 src/backend/optimizer/path/joinrels.c        | 187 +++++++++
 src/backend/optimizer/plan/setrefs.c         |  17 +-
 src/backend/optimizer/util/appendinfo.c      |  51 ++-
 src/backend/optimizer/util/pathnode.c        |   9 +-
 src/backend/optimizer/util/relnode.c         |  19 +-
 src/include/optimizer/paths.h                |   7 +-
 src/test/regress/expected/partition_join.out | 378 +++++++++++++++++++
 src/test/regress/sql/partition_join.sql      | 167 ++++++++
 9 files changed, 808 insertions(+), 36 deletions(-)

diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 6407ede12a..32618ebbd5 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -335,6 +335,15 @@ add_paths_to_joinrel(PlannerInfo *root,
 	if (set_join_pathlist_hook)
 		set_join_pathlist_hook(root, joinrel, outerrel, innerrel,
 							   jointype, &extra);
+
+	/*
+	 * 7. If outer relation is delivered from partition-tables, consider
+	 * distributing inner relation to every partition-leaf prior to
+	 * append these leafs.
+	 */
+	try_asymmetric_partitionwise_join(root, joinrel,
+									  outerrel, innerrel,
+									  jointype, &extra);
 }
 
 /*
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 8b69870cf4..9453258f83 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -16,6 +16,7 @@
 
 #include "miscadmin.h"
 #include "optimizer/appendinfo.h"
+#include "optimizer/cost.h"
 #include "optimizer/joininfo.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
@@ -1552,6 +1553,192 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
 	}
 }
 
+/*
+ * Build RelOptInfo on JOIN of each partition of the outer relation and the inner
+ * relation. Return List of such RelOptInfo's. Return NIL, if at least one of
+ * these JOINs is impossible to build.
+ */
+static List *
+extract_asymmetric_partitionwise_subjoin(PlannerInfo *root,
+										 RelOptInfo *joinrel,
+										 AppendPath *append_path,
+										 RelOptInfo *inner_rel,
+										 JoinType jointype,
+										 JoinPathExtraData *extra)
+{
+	List		*result = NIL;
+	ListCell	*lc;
+
+	foreach (lc, append_path->subpaths)
+	{
+		Path			*child_path = lfirst(lc);
+		RelOptInfo		*child_rel = child_path->parent;
+		Relids			child_joinrelids;
+		Relids			parent_relids;
+		RelOptInfo		*child_joinrel;
+		SpecialJoinInfo	*child_sjinfo;
+		List			*child_restrictlist;
+
+		child_joinrelids = bms_union(child_rel->relids, inner_rel->relids);
+		parent_relids = bms_union(append_path->path.parent->relids,
+								  inner_rel->relids);
+
+		child_sjinfo = build_child_join_sjinfo(root, extra->sjinfo,
+											   child_rel->relids,
+											   inner_rel->relids);
+		child_restrictlist = (List *)
+			adjust_appendrel_attrs_multilevel(root, (Node *)extra->restrictlist,
+											  child_joinrelids, parent_relids);
+
+		child_joinrel = find_join_rel(root, child_joinrelids);
+		if (!child_joinrel)
+			child_joinrel = build_child_join_rel(root,
+												 child_rel,
+												 inner_rel,
+												 joinrel,
+												 child_restrictlist,
+												 child_sjinfo,
+												 jointype);
+		else
+		{
+			/*
+			 * The join relation already exists. For example, it could happen if
+			 * we join two plane tables with partitioned table(s).
+			 * Populating this join with additional paths could push out some
+			 * previously added paths which could be pointed in a subplans list
+			 * of an higher level append.
+			 * Of course, we could save such paths before generating new. But it
+			 * can increase too much the number of paths in complex queries. It
+			 * can be a task for future work.
+			 */
+			return NIL;
+		}
+
+		populate_joinrel_with_paths(root,
+									child_rel,
+									inner_rel,
+									child_joinrel,
+									child_sjinfo,
+									child_restrictlist);
+
+		/* Give up if asymmetric partition-wise join is not available */
+		if (child_joinrel->pathlist == NIL)
+			return NIL;
+
+		set_cheapest(child_joinrel);
+		result = lappend(result, child_joinrel);
+	}
+	return result;
+}
+
+static bool
+is_asymmetric_join_feasible(PlannerInfo *root,
+						   RelOptInfo *outer_rel,
+						   RelOptInfo *inner_rel,
+						   JoinType jointype)
+{
+	ListCell *lc;
+
+	if (jointype != JOIN_INNER && jointype != JOIN_LEFT)
+		return false;
+
+	if (IS_OTHER_REL(outer_rel) || IS_OTHER_REL(inner_rel))
+		return false;
+
+	/* Disallow recursive usage of asymmertic join machinery */
+	if (root->join_rel_level == NULL)
+		return false;
+
+	/*
+	 * Don't allow asymmetric JOIN of two append subplans.
+	 * In the case of a parameterized NL join, a reparameterization procedure
+	 * will lead to large memory allocations and a CPU consumption:
+	 * each reparameterization will induce subpath duplication, creating new
+	 * ParamPathInfo instance and increasing of ppilist up to number of
+	 * partitions in the inner. Also, if we have many partitions, each bitmapset
+	 * variable will be large and many leaks of such variable (caused by relid
+	 * replacement) will highly increase memory consumption.
+	 * So, we deny such paths for now.
+	 */
+	foreach(lc, inner_rel->pathlist)
+	{
+		if (IsA(lfirst(lc), AppendPath))
+			return false;
+	}
+
+	return true;
+}
+
+void
+try_asymmetric_partitionwise_join(PlannerInfo *root,
+								  RelOptInfo *joinrel,
+								  RelOptInfo *outer_rel,
+								  RelOptInfo *inner_rel,
+								  JoinType jointype,
+								  JoinPathExtraData *extra)
+{
+	ListCell *lc;
+
+	/*
+	 * Try this kind of paths if we allow complex partitionwise joins and we know
+	 * we can build this join safely.
+	 */
+	if (!enable_partitionwise_join ||
+		!is_asymmetric_join_feasible(root, outer_rel, inner_rel, jointype))
+		return;
+
+	foreach (lc, outer_rel->pathlist)
+	{
+		AppendPath *append_path = lfirst(lc);
+
+		/*
+		 * We assume this pathlist keeps at least one AppendPath that
+		 * represents partitioned table-scan, symmetric or asymmetric
+		 * partition-wise join. Asymmetric join isn't needed if the append node
+		 * has only one child.
+		 */
+		if (IsA(append_path, AppendPath) &&
+			list_length(append_path->subpaths) > 1)
+		{
+			List **join_rel_level_saved;
+			List *live_childrels = NIL;
+
+			join_rel_level_saved = root->join_rel_level;
+			PG_TRY();
+			{
+				/* temporary disables "dynamic programming" algorithm */
+				root->join_rel_level = NULL;
+
+				live_childrels =
+					extract_asymmetric_partitionwise_subjoin(root,
+															 joinrel,
+															 append_path,
+															 inner_rel,
+															 jointype,
+															 extra);
+			}
+			PG_FINALLY();
+			{
+				root->join_rel_level = join_rel_level_saved;
+			}
+			PG_END_TRY();
+
+			if (live_childrels != NIL)
+			{
+				/*
+				 * Add new append relation. We must choose cheapest paths after
+				 * this operation because the pathlist possibly contains
+				 * joinrels and appendrels that can be suboptimal.
+				 */
+				add_paths_to_append_rel(root, joinrel, live_childrels);
+				set_cheapest(joinrel);
+			}
+
+			break;
+		}
+	}
+}
+
 /*
  * Construct the SpecialJoinInfo for a child-join by translating
  * SpecialJoinInfo for the join between parents. left_relids and right_relids
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index e50624c465..fccc0685d7 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -281,24 +281,29 @@ set_plan_references(PlannerInfo *root, Plan *plan)
 
 	/*
 	 * Adjust RT indexes of AppendRelInfos and add to final appendrels list.
-	 * We assume the AppendRelInfos were built during planning and don't need
-	 * to be copied.
+	 * The AppendRelInfos are copied, because as a part of a subplan they could
+	 * be visited many times in the case of asymmetric join.
 	 */
 	foreach(lc, root->append_rel_list)
 	{
 		AppendRelInfo *appinfo = lfirst_node(AppendRelInfo, lc);
+		AppendRelInfo *newappinfo;
+
+		/* flat copy is enough since all valuable fields are scalars */
+		newappinfo = (AppendRelInfo *) palloc(sizeof(AppendRelInfo));
+		memcpy(newappinfo, appinfo, sizeof(AppendRelInfo));
 
 		/* adjust RT indexes */
-		appinfo->parent_relid += rtoffset;
-		appinfo->child_relid += rtoffset;
+		newappinfo->parent_relid += rtoffset;
+		newappinfo->child_relid += rtoffset;
 
 		/*
 		 * Rather than adjust the translated_vars entries, just drop 'em.
 		 * Neither the executor nor EXPLAIN currently need that data.
 		 */
-		appinfo->translated_vars = NIL;
+		newappinfo->translated_vars = NIL;
 
-		glob->appendRelations = lappend(glob->appendRelations, appinfo);
+		glob->appendRelations = lappend(glob->appendRelations, newappinfo);
 	}
 
 	/* Now fix the Plan tree */
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index af46f581ac..3ae25f3444 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -202,7 +202,9 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
 	context.appinfos = appinfos;
 
 	/* If there's nothing to adjust, don't call this function. */
-	Assert(nappinfos >= 1 && appinfos != NULL);
+	/* If there's nothing to adjust, just return a duplication */
+	if (nappinfos == 0)
+		return copyObject(node);
 
 	/* Should never be translating a Query tree. */
 	Assert(node == NULL || !IsA(node, Query));
@@ -489,12 +491,10 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 								  Relids child_relids,
 								  Relids top_parent_relids)
 {
-	AppendRelInfo **appinfos;
-	Bitmapset  *parent_relids = NULL;
-	int			nappinfos;
-	int			cnt;
-
-	Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids));
+	AppendRelInfo	**appinfos;
+	Relids			parent_relids = NULL;
+	int				nappinfos;
+	int				cnt;
 
 	appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos);
 
@@ -506,8 +506,13 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 		parent_relids = bms_add_member(parent_relids, appinfo->parent_relid);
 	}
 
-	/* Recurse if immediate parent is not the top parent. */
-	if (!bms_equal(parent_relids, top_parent_relids))
+	/*
+	 * Recurse if immediate parent is not the top parent. Keep in mind that in a
+	 * case of asymmetric JOIN top_parent_relids can contain relids which aren't
+	 * part of an append node.
+	 */
+	if (!bms_equal(parent_relids, top_parent_relids) &&
+		!bms_is_subset(parent_relids, top_parent_relids))
 		node = adjust_appendrel_attrs_multilevel(root, node, parent_relids,
 												 top_parent_relids);
 
@@ -515,12 +520,13 @@ adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
 	node = adjust_appendrel_attrs(root, node, nappinfos, appinfos);
 
 	pfree(appinfos);
+	pfree(parent_relids);
 
 	return node;
 }
 
 /*
- * Substitute child relids for parent relids in a Relid set.  The array of
+ * Substitute child relids for parent relids in a Relid set. The array of
  * appinfos specifies the substitutions to be performed.
  */
 Relids
@@ -565,8 +571,9 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
 	AppendRelInfo **appinfos;
 	int			nappinfos;
 	Relids		parent_relids = NULL;
+	Relids		normal_relids = NULL;
 	Relids		result;
-	Relids		tmp_result = NULL;
+	Relids		tmp_result = relids;
 	int			cnt;
 
 	/*
@@ -579,12 +586,17 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
 	appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos);
 
 	/* Construct relids set for the immediate parent of the given child. */
+	normal_relids = bms_copy(child_relids);
 	for (cnt = 0; cnt < nappinfos; cnt++)
 	{
 		AppendRelInfo *appinfo = appinfos[cnt];
 
 		parent_relids = bms_add_member(parent_relids, appinfo->parent_relid);
+		normal_relids = bms_del_member(normal_relids, appinfo->child_relid);
 	}
+	parent_relids = bms_union(parent_relids, normal_relids);
+	if (normal_relids)
+		bms_free(normal_relids);
 
 	/* Recurse if immediate parent is not the top parent. */
 	if (!bms_equal(parent_relids, top_parent_relids))
@@ -592,13 +604,15 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
 		tmp_result = adjust_child_relids_multilevel(root, relids,
 													parent_relids,
 													top_parent_relids);
-		relids = tmp_result;
 	}
 
-	result = adjust_child_relids(relids, nappinfos, appinfos);
+	result = adjust_child_relids(tmp_result, nappinfos, appinfos);
 
-	/* Free memory consumed by any intermediate result. */
-	if (tmp_result)
+	/*
+	 * Free memory consumed by any intermediate result. If recursive pass did't
+	 * replace any relids, tmp_result point to the original set, not a copy.
+	 */
+	if (tmp_result != relids)
 		bms_free(tmp_result);
 	bms_free(parent_relids);
 	pfree(appinfos);
@@ -715,11 +729,11 @@ AppendRelInfo **
 find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
 {
 	AppendRelInfo **appinfos;
+	int			nrooms = bms_num_members(relids);
 	int			cnt = 0;
 	int			i;
 
-	*nappinfos = bms_num_members(relids);
-	appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos);
+	appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * nrooms);
 
 	i = -1;
 	while ((i = bms_next_member(relids, i)) >= 0)
@@ -727,10 +741,11 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
 		AppendRelInfo *appinfo = root->append_rel_array[i];
 
 		if (!appinfo)
-			elog(ERROR, "child rel %d not found in append_rel_array", i);
+			continue;
 
 		appinfos[cnt++] = appinfo;
 	}
+	*nappinfos = cnt;
 	return appinfos;
 }
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index cedb3848dd..17e215b72d 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -4225,7 +4225,14 @@ do { \
 
 		MemoryContextSwitchTo(oldcontext);
 	}
-	bms_free(required_outer);
+
+	/*
+	 * If adjust_child_relids_multilevel don't do replacements it returns
+	 * the original set, not a copy. It is possible in the case of asymmetric
+	 * JOIN and child_rel->relids contains relids only of plane relations.
+	 */
+	if (required_outer != old_ppi->ppi_req_outer)
+		bms_free(required_outer);
 
 	new_path->param_info = new_ppi;
 
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 47769cea45..ddf0f5a876 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -792,11 +792,8 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
 	AppendRelInfo **appinfos;
 	int			nappinfos;
 
-	/* Only joins between "other" relations land here. */
-	Assert(IS_OTHER_REL(outer_rel) && IS_OTHER_REL(inner_rel));
-
-	/* The parent joinrel should have consider_partitionwise_join set. */
-	Assert(parent_joinrel->consider_partitionwise_join);
+	/* Either of relations must be "other" relation at least. */
+	Assert(IS_OTHER_REL(outer_rel) || IS_OTHER_REL(inner_rel));
 
 	joinrel->reloptkind = RELOPT_OTHER_JOINREL;
 	joinrel->relids = bms_union(outer_rel->relids, inner_rel->relids);
@@ -854,8 +851,11 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
 	joinrel->partexprs = NULL;
 	joinrel->nullable_partexprs = NULL;
 
-	joinrel->top_parent_relids = bms_union(outer_rel->top_parent_relids,
-										   inner_rel->top_parent_relids);
+	joinrel->top_parent_relids =
+		bms_union(IS_OTHER_REL(outer_rel) ?
+				  outer_rel->top_parent_relids : outer_rel->relids,
+				  IS_OTHER_REL(inner_rel) ?
+				  inner_rel->top_parent_relids : inner_rel->relids);
 
 	/* Compute information relevant to foreign relations. */
 	set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
@@ -2036,9 +2036,8 @@ build_child_join_reltarget(PlannerInfo *root,
 {
 	/* Build the targetlist */
 	childrel->reltarget->exprs = (List *)
-		adjust_appendrel_attrs(root,
-							   (Node *) parentrel->reltarget->exprs,
-							   nappinfos, appinfos);
+		adjust_appendrel_attrs_multilevel(root, (Node *)parentrel->reltarget->exprs,
+										childrel->relids, parentrel->relids);
 
 	/* Set the cost and width fields */
 	childrel->reltarget->cost.startup = parentrel->reltarget->cost.startup;
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index f1d111063c..a0106dc539 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -109,7 +109,12 @@ extern bool have_join_order_restriction(PlannerInfo *root,
 extern bool have_dangerous_phv(PlannerInfo *root,
 							   Relids outer_relids, Relids inner_params);
 extern void mark_dummy_rel(RelOptInfo *rel);
-
+extern void try_asymmetric_partitionwise_join(PlannerInfo *root,
+											  RelOptInfo *joinrel,
+											  RelOptInfo *outer_rel,
+											  RelOptInfo *inner_rel,
+											  JoinType jointype,
+											  JoinPathExtraData *extra);
 /*
  * equivclass.c
  *	  routines for managing EquivalenceClasses
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 27f7525b3e..327505676c 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -2320,6 +2320,384 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a =
  375 | 0375 | 375 | 0375
 (8 rows)
 
+--
+-- For asymmetric partition-wise join
+--
+CREATE TABLE prt5 (hkey int, a int, b int) PARTITION BY HASH(hkey);
+CREATE TABLE prt5_p0 PARTITION OF prt5
+                     FOR VALUES WITH (modulus 3, remainder 0);
+CREATE TABLE prt5_p1 PARTITION OF prt5
+                     FOR VALUES WITH (modulus 3, remainder 1);
+CREATE TABLE prt5_p2 PARTITION OF prt5
+                     FOR VALUES WITH (modulus 3, remainder 2);
+CREATE TABLE prt6 (aid int, alabel text) PARTITION BY HASH(aid);
+CREATE TABLE prt6_p0 PARTITION OF prt6
+                     FOR VALUES WITH (modulus 2, remainder 0);
+CREATE TABLE prt6_p1 PARTITION OF prt6
+                     FOR VALUES WITH (modulus 2, remainder 1);
+CREATE TABLE t5_1 (aid int, alabel text);
+CREATE TABLE t5_2 (bid int, blabel text);
+INSERT INTO prt5 (SELECT x, (x % 1000)::int,
+                            ((x+1) % 1000)::int
+                    FROM generate_series(1,1000000) x);
+INSERT INTO t5_1 (SELECT x, md5(x::text) FROM generate_series(-200, 1500) x);
+INSERT INTO t5_2 (SELECT x, md5(x::text) FROM generate_series(-200, 1500) x);
+INSERT INTO prt6 (SELECT * FROM t5_1);
+VACUUM ANALYZE prt5,prt6,t5_1,t5_2;
+SET max_parallel_workers_per_gather = 0;
+-- Trivial asymmetric JOIN of partitioned table with a relation
+EXPLAIN (COSTS OFF)
+SELECT *
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%abc%';
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Append
+   ->  Hash Join
+         Hash Cond: (prt5_1.a = t5_1.aid)
+         ->  Seq Scan on prt5_p0 prt5_1
+         ->  Hash
+               ->  Seq Scan on t5_1
+                     Filter: (alabel ~~ '%abc%'::text)
+   ->  Hash Join
+         Hash Cond: (prt5_2.a = t5_1.aid)
+         ->  Seq Scan on prt5_p1 prt5_2
+         ->  Hash
+               ->  Seq Scan on t5_1
+                     Filter: (alabel ~~ '%abc%'::text)
+   ->  Hash Join
+         Hash Cond: (prt5_3.a = t5_1.aid)
+         ->  Seq Scan on prt5_p2 prt5_3
+         ->  Hash
+               ->  Seq Scan on t5_1
+                     Filter: (alabel ~~ '%abc%'::text)
+(19 rows)
+
+-- The same, but appended with UNION ALL
+EXPLAIN (COSTS OFF)
+SELECT * FROM (
+	(SELECT * FROM prt5_p0)
+	UNION ALL
+	(SELECT * FROM prt5_p1)
+	UNION ALL
+	(SELECT * FROM prt5_p2)
+	) AS sq1
+JOIN t5_1 ON a = aid AND alabel like '%abc%';
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Append
+   ->  Hash Join
+         Hash Cond: (prt5_p0.a = t5_1.aid)
+         ->  Seq Scan on prt5_p0
+         ->  Hash
+               ->  Seq Scan on t5_1
+                     Filter: (alabel ~~ '%abc%'::text)
+   ->  Hash Join
+         Hash Cond: (prt5_p1.a = t5_1.aid)
+         ->  Seq Scan on prt5_p1
+         ->  Hash
+               ->  Seq Scan on t5_1
+                     Filter: (alabel ~~ '%abc%'::text)
+   ->  Hash Join
+         Hash Cond: (prt5_p2.a = t5_1.aid)
+         ->  Seq Scan on prt5_p2
+         ->  Hash
+               ->  Seq Scan on t5_1
+                     Filter: (alabel ~~ '%abc%'::text)
+(19 rows)
+
+-- Don't allow asymmetric JOIN of two partitioned tables.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM prt5 JOIN prt6 ON a = aid AND alabel like '%abc%';
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Hash Join
+         Hash Cond: (prt5.a = prt6.aid)
+         ->  Append
+               ->  Seq Scan on prt5_p0 prt5_1
+               ->  Seq Scan on prt5_p1 prt5_2
+               ->  Seq Scan on prt5_p2 prt5_3
+         ->  Hash
+               ->  Append
+                     ->  Seq Scan on prt6_p0 prt6_1
+                           Filter: (alabel ~~ '%abc%'::text)
+                     ->  Seq Scan on prt6_p1 prt6_2
+                           Filter: (alabel ~~ '%abc%'::text)
+(13 rows)
+
+SELECT count(*) FROM prt5 JOIN prt6 ON a = aid AND alabel like '%abc%';
+ count 
+-------
+  4000
+(1 row)
+
+-- Check asymmetric JOIN with Subquery
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM prt5 JOIN (
+	SELECT * FROM prt6 LIMIT 1000
+) AS sq1 ON a = aid AND alabel like '%abc%';
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Append
+         ->  Hash Join
+               Hash Cond: (prt5_1.a = sq1.aid)
+               ->  Seq Scan on prt5_p0 prt5_1
+               ->  Hash
+                     ->  Subquery Scan on sq1
+                           Filter: (sq1.alabel ~~ '%abc%'::text)
+                           ->  Limit
+                                 ->  Append
+                                       ->  Seq Scan on prt6_p0 prt6_1
+                                       ->  Seq Scan on prt6_p1 prt6_2
+         ->  Hash Join
+               Hash Cond: (prt5_2.a = sq1.aid)
+               ->  Seq Scan on prt5_p1 prt5_2
+               ->  Hash
+                     ->  Subquery Scan on sq1
+                           Filter: (sq1.alabel ~~ '%abc%'::text)
+                           ->  Limit
+                                 ->  Append
+                                       ->  Seq Scan on prt6_p0 prt6_4
+                                       ->  Seq Scan on prt6_p1 prt6_5
+         ->  Hash Join
+               Hash Cond: (prt5_3.a = sq1.aid)
+               ->  Seq Scan on prt5_p2 prt5_3
+               ->  Hash
+                     ->  Subquery Scan on sq1
+                           Filter: (sq1.alabel ~~ '%abc%'::text)
+                           ->  Limit
+                                 ->  Append
+                                       ->  Seq Scan on prt6_p0 prt6_7
+                                       ->  Seq Scan on prt6_p1 prt6_8
+(32 rows)
+
+SELECT count(*) FROM prt5 JOIN (SELECT * FROM prt6 LIMIT 1000) AS sq1
+	ON a = aid AND alabel like '%abc%';
+ count 
+-------
+  2000
+(1 row)
+
+-- Asymmetric JOIN of two plane tables and one partitioned
+EXPLAIN (COSTS OFF)
+SELECT count(*)
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%ab%'
+            JOIN t5_2 ON b = bid AND blabel like '%cd%';
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Append
+         ->  Hash Join
+               Hash Cond: (prt5_1.b = t5_2.bid)
+               ->  Hash Join
+                     Hash Cond: (prt5_1.a = t5_1.aid)
+                     ->  Seq Scan on prt5_p0 prt5_1
+                     ->  Hash
+                           ->  Seq Scan on t5_1
+                                 Filter: (alabel ~~ '%ab%'::text)
+               ->  Hash
+                     ->  Seq Scan on t5_2
+                           Filter: (blabel ~~ '%cd%'::text)
+         ->  Hash Join
+               Hash Cond: (prt5_2.b = t5_2.bid)
+               ->  Hash Join
+                     Hash Cond: (prt5_2.a = t5_1.aid)
+                     ->  Seq Scan on prt5_p1 prt5_2
+                     ->  Hash
+                           ->  Seq Scan on t5_1
+                                 Filter: (alabel ~~ '%ab%'::text)
+               ->  Hash
+                     ->  Seq Scan on t5_2
+                           Filter: (blabel ~~ '%cd%'::text)
+         ->  Hash Join
+               Hash Cond: (prt5_3.b = t5_2.bid)
+               ->  Hash Join
+                     Hash Cond: (prt5_3.a = t5_1.aid)
+                     ->  Seq Scan on prt5_p2 prt5_3
+                     ->  Hash
+                           ->  Seq Scan on t5_1
+                                 Filter: (alabel ~~ '%ab%'::text)
+               ->  Hash
+                     ->  Seq Scan on t5_2
+                           Filter: (blabel ~~ '%cd%'::text)
+(35 rows)
+
+SELECT count(*)
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%ab%'
+            JOIN t5_2 ON b = bid AND blabel like '%cd%';
+ count 
+-------
+ 11000
+(1 row)
+
+-- unable to extract non-partitioned right relation
+EXPLAIN (COSTS OFF)
+SELECT * FROM prt5 RIGHT JOIN t5_1 ON a = aid AND alabel like '%abc%';
+                  QUERY PLAN                   
+-----------------------------------------------
+ Hash Right Join
+   Hash Cond: (prt5.a = t5_1.aid)
+   Join Filter: (t5_1.alabel ~~ '%abc%'::text)
+   ->  Append
+         ->  Seq Scan on prt5_p0 prt5_1
+         ->  Seq Scan on prt5_p1 prt5_2
+         ->  Seq Scan on prt5_p2 prt5_3
+   ->  Hash
+         ->  Seq Scan on t5_1
+(9 rows)
+
+-- left side can be extracted, but no cost benefit
+EXPLAIN (COSTS OFF)
+SELECT * FROM prt5 LEFT JOIN t5_1 ON a = aid AND alabel like '%abc%';
+                   QUERY PLAN                    
+-------------------------------------------------
+ Hash Left Join
+   Hash Cond: (prt5.a = t5_1.aid)
+   ->  Append
+         ->  Seq Scan on prt5_p0 prt5_1
+         ->  Seq Scan on prt5_p1 prt5_2
+         ->  Seq Scan on prt5_p2 prt5_3
+   ->  Hash
+         ->  Seq Scan on t5_1
+               Filter: (alabel ~~ '%abc%'::text)
+(9 rows)
+
+-- validation of the results with/without asymmetric partition-wise join
+SELECT * INTO pg_temp.result01a
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%abc%';
+SELECT * INTO pg_temp.result02a
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%ab%'
+            JOIN t5_2 ON b = bid AND blabel like '%cd%';
+SET enable_partitionwise_join = off;
+SELECT * INTO pg_temp.result01b
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%abc%';
+SELECT * INTO pg_temp.result02b
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%ab%'
+            JOIN t5_2 ON b = bid AND blabel like '%cd%';
+SELECT * FROM pg_temp.result01a EXCEPT SELECT * FROM pg_temp.result01b;
+ hkey | a | b | aid | alabel 
+------+---+---+-----+--------
+(0 rows)
+
+SELECT * FROM pg_temp.result02a EXCEPT SELECT * FROM pg_temp.result02b;
+ hkey | a | b | aid | alabel | bid | blabel 
+------+---+---+-----+--------+-----+--------
+(0 rows)
+
+SET enable_partitionwise_join = on;
+-- Check reparameterization code when required_outer set contains relid of plain
+-- relation entry, not 'other' entry.
+SET enable_hashjoin = 'off';
+SET enable_mergejoin = 'off';
+SET enable_material = 'off';
+CREATE TABLE big AS SELECT x AS x FROM generate_series(1,1E4) x;
+CREATE INDEX ON big(x);
+CREATE TABLE small AS SELECT x, -x AS y FROM generate_series(1,100) x;
+CREATE TABLE part_l0 (x int, y int, z int) PARTITION BY HASH (y);
+CREATE TABLE part0_l1 PARTITION OF part_l0 (y)
+	FOR VALUES WITH (modulus 2, remainder 0) PARTITION BY HASH (z);
+CREATE TABLE part0_l2 PARTITION OF part0_l1 (z)
+	FOR VALUES WITH (modulus 2, remainder 0);
+CREATE TABLE part1_l2 PARTITION OF part0_l1 (z)
+	FOR VALUES WITH (modulus 2, remainder 1);
+CREATE TABLE part1_l1 PARTITION OF part_l0 (y)
+	FOR VALUES WITH (modulus 2, remainder 1);
+INSERT INTO part_l0 (x, y, z) (SELECT x,x,x FROM generate_series(1,1E4) x);
+ANALYZE big,small,part_l0;
+EXPLAIN
+SELECT small.* FROM small
+	JOIN part_l0 ON small.y = part_l0.y AND small.y + part_l0.y < 10
+	LEFT JOIN big ON big.x = small.x;
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Nested Loop Left Join  (cost=0.29..33230.19 rows=33 width=8)
+   ->  Append  (cost=0.00..33008.08 rows=67 width=8)
+         ->  Nested Loop  (cost=0.00..16604.25 rows=33 width=8)
+               Join Filter: ((small.y = part_l0_1.y) AND ((small.y + part_l0_1.y) < 10))
+               ->  Seq Scan on small  (cost=0.00..2.00 rows=100 width=8)
+               ->  Seq Scan on part0_l2 part_l0_1  (cost=0.00..78.19 rows=5019 width=4)
+         ->  Nested Loop  (cost=0.00..3.75 rows=1 width=8)
+               Join Filter: ((small.y = part_l0_2.y) AND ((small.y + part_l0_2.y) < 10))
+               ->  Seq Scan on part1_l2 part_l0_2  (cost=0.00..0.00 rows=1 width=4)
+               ->  Seq Scan on small  (cost=0.00..2.00 rows=100 width=8)
+         ->  Nested Loop  (cost=0.00..16399.75 rows=33 width=8)
+               Join Filter: ((small.y = part_l0_3.y) AND ((small.y + part_l0_3.y) < 10))
+               ->  Seq Scan on small  (cost=0.00..2.00 rows=100 width=8)
+               ->  Seq Scan on part1_l1 part_l0_3  (cost=0.00..76.81 rows=4981 width=4)
+   ->  Index Only Scan using big_x_idx on big  (cost=0.29..3.31 rows=1 width=5)
+         Index Cond: (x = (small.x)::numeric)
+(16 rows)
+
+DROP TABLE IF EXISTS big,small,part_l0 CASCADE;
+RESET enable_hashjoin;
+RESET enable_mergejoin;
+RESET enable_material;
+RESET max_parallel_workers_per_gather;
+-- Parameterized path examples.
+CREATE TABLE prta (id integer, payload text) PARTITION BY HASH (id);
+CREATE TABLE prta1 PARTITION OF prta FOR VALUES WITH (modulus 2, remainder 0);
+CREATE TABLE prta2 PARTITION OF prta FOR VALUES WITH (modulus 2, remainder 1);
+CREATE UNIQUE INDEX ON prta1 (id);
+CREATE UNIQUE INDEX ON prta2 (id);
+INSERT INTO prta (id, payload)
+	(SELECT *, ('abc' || id)::text AS payload
+		FROM generate_series(1,1) AS id);
+CREATE TABLE prtb (id integer, payload text) PARTITION BY HASH (id);
+CREATE TABLE prtb1 PARTITION OF prtb FOR VALUES WITH (modulus 2, remainder 0);
+CREATE TABLE prtb2 PARTITION OF prtb FOR VALUES WITH (modulus 2, remainder 1);
+CREATE UNIQUE INDEX ON prtb1 (id);
+CREATE UNIQUE INDEX ON prtb2 (id);
+INSERT INTO prtb (id, payload)
+    (SELECT *, ('def' || id)::text AS payload
+		FROM generate_series(1,1000) AS id);
+CREATE TABLE e (id integer, payload text) PARTITION BY HASH (id);
+CREATE TABLE e1 PARTITION OF e FOR VALUES WITH (modulus 2, remainder 0);
+CREATE TABLE e2 PARTITION OF e FOR VALUES WITH (modulus 2, remainder 1);
+INSERT INTO e (id, payload)
+	(SELECT *, ('ghi' || id)::text AS payload
+		FROM generate_series(1,1000) AS id);
+CREATE UNIQUE INDEX ON e1 (id);
+CREATE UNIQUE INDEX ON e2 (id);
+ANALYZE prta,prtb,e;
+EXPLAIN (COSTS OFF)
+SELECT * FROM prta,prtb WHERE prta.id=prtb.id;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Append
+   ->  Nested Loop
+         ->  Seq Scan on prta1 prta_1
+         ->  Index Scan using prtb1_id_idx on prtb1 prtb_1
+               Index Cond: (id = prta_1.id)
+   ->  Nested Loop
+         ->  Seq Scan on prta2 prta_2
+         ->  Index Scan using prtb2_id_idx on prtb2 prtb_2
+               Index Cond: (id = prta_2.id)
+(9 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM prta,prtb,e WHERE prta.id=prtb.id AND prta.id=e.id;
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Append
+   ->  Nested Loop
+         Join Filter: (prta_1.id = e_1.id)
+         ->  Nested Loop
+               ->  Seq Scan on prta1 prta_1
+               ->  Index Scan using prtb1_id_idx on prtb1 prtb_1
+                     Index Cond: (id = prta_1.id)
+         ->  Index Scan using e1_id_idx on e1 e_1
+               Index Cond: (id = prtb_1.id)
+   ->  Nested Loop
+         Join Filter: (prta_2.id = e_2.id)
+         ->  Nested Loop
+               ->  Seq Scan on prta2 prta_2
+               ->  Index Scan using prtb2_id_idx on prtb2 prtb_2
+                     Index Cond: (id = prta_2.id)
+         ->  Index Scan using e2_id_idx on e2 e_2
+               Index Cond: (id = prtb_2.id)
+(17 rows)
+
 -- semi join
 EXPLAIN (COSTS OFF)
 SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
diff --git a/src/test/regress/sql/partition_join.sql b/src/test/regress/sql/partition_join.sql
index d97b5b69ff..364b3216f8 100644
--- a/src/test/regress/sql/partition_join.sql
+++ b/src/test/regress/sql/partition_join.sql
@@ -536,6 +536,173 @@ EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_adv t1 INNER JOIN prt2_adv t2 ON (t1.a = t2.b) WHERE t1.b = 0 ORDER BY t1.a, t2.b;
 
+--
+-- For asymmetric partition-wise join
+--
+CREATE TABLE prt5 (hkey int, a int, b int) PARTITION BY HASH(hkey);
+CREATE TABLE prt5_p0 PARTITION OF prt5
+                     FOR VALUES WITH (modulus 3, remainder 0);
+CREATE TABLE prt5_p1 PARTITION OF prt5
+                     FOR VALUES WITH (modulus 3, remainder 1);
+CREATE TABLE prt5_p2 PARTITION OF prt5
+                     FOR VALUES WITH (modulus 3, remainder 2);
+CREATE TABLE prt6 (aid int, alabel text) PARTITION BY HASH(aid);
+CREATE TABLE prt6_p0 PARTITION OF prt6
+                     FOR VALUES WITH (modulus 2, remainder 0);
+CREATE TABLE prt6_p1 PARTITION OF prt6
+                     FOR VALUES WITH (modulus 2, remainder 1);
+CREATE TABLE t5_1 (aid int, alabel text);
+CREATE TABLE t5_2 (bid int, blabel text);
+
+INSERT INTO prt5 (SELECT x, (x % 1000)::int,
+                            ((x+1) % 1000)::int
+                    FROM generate_series(1,1000000) x);
+INSERT INTO t5_1 (SELECT x, md5(x::text) FROM generate_series(-200, 1500) x);
+INSERT INTO t5_2 (SELECT x, md5(x::text) FROM generate_series(-200, 1500) x);
+INSERT INTO prt6 (SELECT * FROM t5_1);
+
+VACUUM ANALYZE prt5,prt6,t5_1,t5_2;
+
+SET max_parallel_workers_per_gather = 0;
+
+-- Trivial asymmetric JOIN of partitioned table with a relation
+EXPLAIN (COSTS OFF)
+SELECT *
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%abc%';
+
+-- The same, but appended with UNION ALL
+EXPLAIN (COSTS OFF)
+SELECT * FROM (
+	(SELECT * FROM prt5_p0)
+	UNION ALL
+	(SELECT * FROM prt5_p1)
+	UNION ALL
+	(SELECT * FROM prt5_p2)
+	) AS sq1
+JOIN t5_1 ON a = aid AND alabel like '%abc%';
+
+-- Don't allow asymmetric JOIN of two partitioned tables.
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM prt5 JOIN prt6 ON a = aid AND alabel like '%abc%';
+SELECT count(*) FROM prt5 JOIN prt6 ON a = aid AND alabel like '%abc%';
+
+-- Check asymmetric JOIN with Subquery
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM prt5 JOIN (
+	SELECT * FROM prt6 LIMIT 1000
+) AS sq1 ON a = aid AND alabel like '%abc%';
+SELECT count(*) FROM prt5 JOIN (SELECT * FROM prt6 LIMIT 1000) AS sq1
+	ON a = aid AND alabel like '%abc%';
+
+-- Asymmetric JOIN of two plane tables and one partitioned
+EXPLAIN (COSTS OFF)
+SELECT count(*)
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%ab%'
+            JOIN t5_2 ON b = bid AND blabel like '%cd%';
+SELECT count(*)
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%ab%'
+            JOIN t5_2 ON b = bid AND blabel like '%cd%';
+
+-- unable to extract non-partitioned right relation
+EXPLAIN (COSTS OFF)
+SELECT * FROM prt5 RIGHT JOIN t5_1 ON a = aid AND alabel like '%abc%';
+-- left side can be extracted, but no cost benefit
+EXPLAIN (COSTS OFF)
+SELECT * FROM prt5 LEFT JOIN t5_1 ON a = aid AND alabel like '%abc%';
+
+-- validation of the results with/without asymmetric partition-wise join
+SELECT * INTO pg_temp.result01a
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%abc%';
+
+SELECT * INTO pg_temp.result02a
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%ab%'
+            JOIN t5_2 ON b = bid AND blabel like '%cd%';
+
+SET enable_partitionwise_join = off;
+
+SELECT * INTO pg_temp.result01b
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%abc%';
+
+SELECT * INTO pg_temp.result02b
+  FROM prt5 JOIN t5_1 ON a = aid AND alabel like '%ab%'
+            JOIN t5_2 ON b = bid AND blabel like '%cd%';
+
+SELECT * FROM pg_temp.result01a EXCEPT SELECT * FROM pg_temp.result01b;
+SELECT * FROM pg_temp.result02a EXCEPT SELECT * FROM pg_temp.result02b;
+
+SET enable_partitionwise_join = on;
+
+-- Check reparameterization code when required_outer set contains relid of plain
+-- relation entry, not 'other' entry.
+
+SET enable_hashjoin = 'off';
+SET enable_mergejoin = 'off';
+SET enable_material = 'off';
+
+CREATE TABLE big AS SELECT x AS x FROM generate_series(1,1E4) x;
+CREATE INDEX ON big(x);
+CREATE TABLE small AS SELECT x, -x AS y FROM generate_series(1,100) x;
+
+CREATE TABLE part_l0 (x int, y int, z int) PARTITION BY HASH (y);
+CREATE TABLE part0_l1 PARTITION OF part_l0 (y)
+	FOR VALUES WITH (modulus 2, remainder 0) PARTITION BY HASH (z);
+CREATE TABLE part0_l2 PARTITION OF part0_l1 (z)
+	FOR VALUES WITH (modulus 2, remainder 0);
+CREATE TABLE part1_l2 PARTITION OF part0_l1 (z)
+	FOR VALUES WITH (modulus 2, remainder 1);
+CREATE TABLE part1_l1 PARTITION OF part_l0 (y)
+	FOR VALUES WITH (modulus 2, remainder 1);
+INSERT INTO part_l0 (x, y, z) (SELECT x,x,x FROM generate_series(1,1E4) x);
+
+ANALYZE big,small,part_l0;
+
+EXPLAIN
+SELECT small.* FROM small
+	JOIN part_l0 ON small.y = part_l0.y AND small.y + part_l0.y < 10
+	LEFT JOIN big ON big.x = small.x;
+
+DROP TABLE IF EXISTS big,small,part_l0 CASCADE;
+RESET enable_hashjoin;
+RESET enable_mergejoin;
+RESET enable_material;
+RESET max_parallel_workers_per_gather;
+
+-- Parameterized path examples.
+CREATE TABLE prta (id integer, payload text) PARTITION BY HASH (id);
+CREATE TABLE prta1 PARTITION OF prta FOR VALUES WITH (modulus 2, remainder 0);
+CREATE TABLE prta2 PARTITION OF prta FOR VALUES WITH (modulus 2, remainder 1);
+CREATE UNIQUE INDEX ON prta1 (id);
+CREATE UNIQUE INDEX ON prta2 (id);
+INSERT INTO prta (id, payload)
+	(SELECT *, ('abc' || id)::text AS payload
+		FROM generate_series(1,1) AS id);
+
+CREATE TABLE prtb (id integer, payload text) PARTITION BY HASH (id);
+CREATE TABLE prtb1 PARTITION OF prtb FOR VALUES WITH (modulus 2, remainder 0);
+CREATE TABLE prtb2 PARTITION OF prtb FOR VALUES WITH (modulus 2, remainder 1);
+CREATE UNIQUE INDEX ON prtb1 (id);
+CREATE UNIQUE INDEX ON prtb2 (id);
+INSERT INTO prtb (id, payload)
+    (SELECT *, ('def' || id)::text AS payload
+		FROM generate_series(1,1000) AS id);
+
+CREATE TABLE e (id integer, payload text) PARTITION BY HASH (id);
+CREATE TABLE e1 PARTITION OF e FOR VALUES WITH (modulus 2, remainder 0);
+CREATE TABLE e2 PARTITION OF e FOR VALUES WITH (modulus 2, remainder 1);
+INSERT INTO e (id, payload)
+	(SELECT *, ('ghi' || id)::text AS payload
+		FROM generate_series(1,1000) AS id);
+CREATE UNIQUE INDEX ON e1 (id);
+CREATE UNIQUE INDEX ON e2 (id);
+
+ANALYZE prta,prtb,e;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM prta,prtb WHERE prta.id=prtb.id;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM prta,prtb,e WHERE prta.id=prtb.id AND prta.id=e.id;
+
 -- semi join
 EXPLAIN (COSTS OFF)
 SELECT t1.* FROM prt1_adv t1 WHERE EXISTS (SELECT 1 FROM prt2_adv t2 WHERE t1.a = t2.b) AND t1.b = 0 ORDER BY t1.a;
-- 
2.25.1


--------------3592BFA68FFAC61CCE0AF115--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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

* [PATCH 2/2] Publish list of tables being repacked in shared memory
@ 2026-04-07 20:29  Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 399+ messages in thread

From: Álvaro Herrera @ 2026-04-07 20:29 UTC (permalink / raw)

Use it in autovacuum to skip processing tables that are being repacked.
This is mostly to avoid repeated attempts to process such tables, which
would fail due to the special deadlock checker behavior for repack.

Author: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/202604072027.gazfa2zt2l2j@alvherre.pgsql
---
 src/backend/commands/repack.c                 | 195 ++++++++++++++++--
 src/backend/postmaster/autovacuum.c           |  20 ++
 .../utils/activity/wait_event_names.txt       |   1 +
 src/include/commands/repack.h                 |   2 +
 src/include/storage/lwlocklist.h              |   2 +-
 src/include/storage/subsystemlist.h           |   1 +
 src/tools/pgindent/typedefs.list              |   3 +
 7 files changed, 210 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a5f5df77291..ee7072dce6a 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -63,9 +63,11 @@
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
+#include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
+#include "storage/subsystems.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -79,6 +81,32 @@
 #include "utils/syscache.h"
 #include "utils/wait_event_types.h"
 
+
+/* Shared memory layout for REPACK */
+typedef struct RepackWorkerInfo
+{
+	bool		ri_in_use;
+	pid_t		ri_backendpid;
+	Oid			ri_dbid;
+	Oid			ri_relid;
+	Oid			ri_toastrelid;
+} RepackWorkerInfo;
+
+typedef struct
+{
+	bool		re_useless;
+	RepackWorkerInfo re_workerinfo[FLEXIBLE_ARRAY_MEMBER];
+} RepackShmemStruct;
+
+static RepackShmemStruct *RepackShmem;
+
+typedef struct RepackCleanupContext
+{
+	bool		concurrent;
+	int			workerindex;
+} RepackCleanupContext;
+
+
 /*
  * This struct is used to pass around the information on tables to be
  * clustered. We need this so we can make a list of them when invoked without
@@ -90,6 +118,7 @@ typedef struct
 	Oid			indexOid;
 } RelToCluster;
 
+
 /*
  * The first file exported by the decoding worker must contain a snapshot, the
  * following ones contain the data changes.
@@ -166,6 +195,10 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd,
 											  MemoryContext permcxt);
 static bool repack_is_permitted_for_relation(RepackCommand cmd,
 											 Oid relid, Oid userid);
+static void RepackCleanup(RepackCleanupContext *context);
+static void RepackCleanupCb(int code, Datum arg);
+static void RepackShmemRequest(void *arg);
+static void RepackShmemInit(void *arg);
 
 static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
 static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
@@ -210,6 +243,11 @@ static void ProcessRepackMessage(StringInfo msg);
 static const char *RepackCommandAsString(RepackCommand cmd);
 
 
+const ShmemCallbacks RepackShmemCallbacks = {
+	.request_fn = RepackShmemRequest,
+	.init_fn = RepackShmemInit,
+};
+
 /*
  * The repack code allows for processing multiple tables at once. Because
  * of this, we cannot just run everything on a single transaction, or we
@@ -514,6 +552,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
+	RepackCleanupContext context;
 	Oid			save_userid;
 	int			save_sec_context;
 	int			save_nestlevel;
@@ -660,24 +699,43 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		TransferPredicateLocksToHeapRelation(OldHeap);
 
 	/* rebuild_relation does all the dirty work */
-	PG_TRY();
-	{
-		rebuild_relation(OldHeap, index, verbose, ident_idx);
-	}
-	PG_FINALLY();
+	context.concurrent = concurrent;
+
+	PG_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
 	{
 		if (concurrent)
 		{
-			/*
-			 * Since during normal operation the worker was already asked to
-			 * exit, stopping it explicitly is especially important on ERROR.
-			 * However it still seems a good practice to make sure that the
-			 * worker never survives the REPACK command.
-			 */
-			stop_repack_decoding_worker();
+			bool		freefound = false;
+
+			LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+			for (int i = 0; i < max_repack_replication_slots; i++)
+			{
+				RepackWorkerInfo *worker;
+
+				if (RepackShmem->re_workerinfo[i].ri_in_use)
+					continue;
+
+				freefound = true;
+				worker = &RepackShmem->re_workerinfo[i];
+				context.workerindex = i;
+
+				worker->ri_in_use = true;
+				worker->ri_backendpid = MyProcPid;
+				worker->ri_dbid = MyDatabaseId;
+				worker->ri_relid = RelationGetRelid(OldHeap);
+				worker->ri_toastrelid = OldHeap->rd_rel->reltoastrelid;
+				break;
+			}
+			if (!freefound)
+				elog(ERROR, "could not find free repack entry");
+			LWLockRelease(RepackLock);
 		}
+
+		rebuild_relation(OldHeap, index, verbose, ident_idx);
 	}
-	PG_END_TRY();
+	PG_END_ENSURE_ERROR_CLEANUP(RepackCleanupCb, PointerGetDatum(&context));
+
+	RepackCleanup(&context);
 
 	/* rebuild_relation closes OldHeap, and index if valid */
 
@@ -691,6 +749,117 @@ out:
 	pgstat_progress_end_command();
 }
 
+/*
+ * Return whether any backend is running concurrent REPACK on the given table
+ * (which could be a toast table).
+ */
+bool
+is_table_under_repack(Oid databaseId, Oid relid)
+{
+	bool		retval = false;
+
+	LWLockAcquire(RepackLock, LW_SHARED);
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		RepackWorkerInfo *rworker;
+
+		if (!RepackShmem->re_workerinfo[i].ri_in_use)
+			continue;
+
+		rworker = &RepackShmem->re_workerinfo[i];
+		if (rworker->ri_dbid == MyDatabaseId &&
+			(rworker->ri_relid == relid ||
+			 rworker->ri_toastrelid == relid))
+			retval = true;
+	}
+	LWLockRelease(RepackLock);
+
+	return retval;
+}
+
+/*
+ * Remove ourselves from the workerinfo array.
+ */
+static void
+RepackCleanup(RepackCleanupContext *context)
+{
+	if (context->concurrent)
+	{
+		RepackWorkerInfo *worker;
+
+		/*
+		 * The worker would normally terminate on its own when the work is
+		 * done, but make sure we signal it just in case.
+		 */
+		stop_repack_decoding_worker();
+
+		/*
+		 * also, make sure we stop advertising the relation we were repacking,
+		 * so that autovacuum reverts to handling it normally.
+		 */
+		LWLockAcquire(RepackLock, LW_EXCLUSIVE);
+
+		worker = &RepackShmem->re_workerinfo[context->workerindex];
+		Assert(worker->ri_backendpid == MyProcPid);
+		worker->ri_in_use = false;
+		worker->ri_backendpid = 0;
+		worker->ri_dbid = InvalidOid;
+		worker->ri_relid = InvalidOid;
+		worker->ri_toastrelid = InvalidOid;
+		LWLockRelease(RepackLock);
+	}
+}
+
+/*
+ * RepackCleanup wrapped as an on_shmem_exit callback function
+ */
+static void
+RepackCleanupCb(int code, Datum arg)
+{
+	RepackCleanup((RepackCleanupContext *) DatumGetPointer(arg));
+}
+
+/*
+ * RepackShmemRequest
+ *		Register shared memory space needed for repack
+ */
+static void
+RepackShmemRequest(void *arg)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of RepackWorkerInfo.
+	 */
+	size = sizeof(RepackShmemStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_repack_replication_slots,
+								   sizeof(RepackWorkerInfo)));
+
+	ShmemRequestStruct(.name = "Repack Data",
+					   .size = size,
+					   .ptr = (void **) &RepackShmem,
+		);
+}
+
+static void
+RepackShmemInit(void *arg)
+{
+	RepackWorkerInfo *reinfo;
+
+	reinfo = (RepackWorkerInfo *) ((char *) RepackShmem +
+								   MAXALIGN(sizeof(RepackShmemStruct)));
+
+	for (int i = 0; i < max_repack_replication_slots; i++)
+	{
+		reinfo[i].ri_in_use = false;
+		reinfo[i].ri_backendpid = 0;
+		reinfo[i].ri_dbid = InvalidOid;
+		reinfo[i].ri_relid = InvalidOid;
+		reinfo[i].ri_toastrelid = InvalidOid;
+	}
+}
+
 /*
  * Check if the table (and its index) still meets the requirements of
  * cluster_rel().
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index bd626a16363..080c64ea3c8 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -78,6 +78,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
@@ -2422,6 +2423,25 @@ do_autovacuum(void)
 			}
 		}
 		LWLockRelease(AutovacuumLock);
+
+		/*
+		 * Similarly, if the table is being processed by concurrent repack,
+		 * skip it (but make a note of that).  We wouldn't be able to acquire
+		 * its lock anyway.
+		 */
+		if (!skipit)
+		{
+			MemoryContextSwitchTo(PortalContext);
+
+			skipit = is_table_under_repack(MyDatabaseId, relid);
+			if (skipit)
+				ereport(LOG,
+						errmsg("skipping table \"%s.%s.%s\" because it's being repacked in concurrent mode",
+							   get_database_name(MyDatabaseId),
+							   get_namespace_name(get_rel_namespace(relid)),
+							   get_rel_name(relid)));
+		}
+
 		if (skipit)
 		{
 			LWLockRelease(AutovacuumScheduleLock);
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 7bda5298558..e206304f204 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -332,6 +332,7 @@ SInvalWrite	"Waiting to add a message to the shared catalog invalidation queue."
 WALBufMapping	"Waiting to replace a page in WAL buffers."
 WALWrite	"Waiting for WAL buffers to be written to disk."
 ControlFile	"Waiting to read or update the <filename>pg_control</filename> file or create a new WAL file."
+Repack	"Waiting to read or update tables in process by concurrent repack."
 MultiXactGen	"Waiting to read or update shared multixact state."
 RelCacheInit	"Waiting to read or update a <filename>pg_internal.init</filename> relation cache initialization file."
 CheckpointerComm	"Waiting to manage fsync requests."
diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h
index fd16e74b179..be7d38b5fae 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -42,6 +42,8 @@ extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid,
 						ClusterParams *params, bool isTopLevel);
+extern bool is_table_under_repack(Oid databaseId, Oid relid);
+
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
 									   LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index af8553bcb6c..3f08f4a15d4 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -41,7 +41,7 @@ PG_LWLOCK(6, SInvalWrite)
 PG_LWLOCK(7, WALBufMapping)
 PG_LWLOCK(8, WALWrite)
 PG_LWLOCK(9, ControlFile)
-/* 10 was CheckpointLock */
+PG_LWLOCK(10, Repack)
 /* 11 was XactSLRULock */
 /* 12 was SubtransSLRULock */
 PG_LWLOCK(13, MultiXactGen)
diff --git a/src/include/storage/subsystemlist.h b/src/include/storage/subsystemlist.h
index 9ad619080be..4e683b8b0a8 100644
--- a/src/include/storage/subsystemlist.h
+++ b/src/include/storage/subsystemlist.h
@@ -72,6 +72,7 @@ PG_SHMEM_SUBSYSTEM(WalSummarizerShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(PgArchShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(ApplyLauncherShmemCallbacks)
 PG_SHMEM_SUBSYSTEM(SlotSyncShmemCallbacks)
+PG_SHMEM_SUBSYSTEM(RepackShmemCallbacks)
 
 /* other modules that need some shared memory space */
 PG_SHMEM_SUBSYSTEM(BTreeShmemCallbacks)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 637c669a146..d019e03aaf1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2639,9 +2639,12 @@ ReorderBufferTupleCidEnt
 ReorderBufferTupleCidKey
 ReorderBufferUpdateProgressTxnCB
 ReorderTuple
+RepackCleanupContext
 RepackCommand
 RepackDecodingState
+RepackShmemStruct
 RepackStmt
+RepackWorkerInfo
 ReparameterizeForeignPathByChild_function
 ReplOriginId
 ReplOriginXactState
-- 
2.47.3


--kdrcpfmkbkc4lqhu--





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


end of thread, other threads:[~2026-04-07 20:29 UTC | newest]

Thread overview: 399+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2021-04-02 06:02 [PATCH] Asymmetric partitionwise join. Andrey Lepikhov <a.lepikhov@postgrespro.ru>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>
2026-04-07 20:29 [PATCH 2/2] Publish list of tables being repacked in shared memory Álvaro Herrera <alvherre@kurilemu.de>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox